BEGIN;

-- DROP LANGUAGE PLPGSQL CASCADE;
-- CREATE LANGUAGE PLPGSQL;

CREATE OR REPLACE FUNCTION premiera_filmu(id_filmu INTEGER)
    RETURNS timestamp with time zone AS
$$
BEGIN
    RETURN premiera_w_polsce FROM kinorezerwacja_film WHERE id = id_filmu;
END; $$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION sprawdz_czy_awaria_ok(czego varchar, id_obiektu integer)
    RETURNS integer AS
$$
BEGIN
    IF czego = 'sali' THEN RETURN COUNT(*) FROM kinorezerwacja_sala WHERE id = id_obiektu;
    ELSE RETURN COUNT(*) FROM kinorezerwacja_miejsce WHERE id = id_obiektu;
    END IF;
END; $$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION kolejny_seans(czas_start timestamp with time zone, id_tej_sali integer) 
    RETURNS integer AS
$$
BEGIN
    RETURN id FROM kinorezerwacja_seans WHERE id_sali = id_tej_sali AND rozpoczecie > czas_start ORDER BY rozpoczecie LIMIT 1;
END; $$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION poprzedni_seans(czas_start timestamp with time zone, id_tej_sali integer)
    RETURNS integer AS
$$
BEGIN
    RETURN id FROM kinorezerwacja_seans WHERE id_sali = id_tej_sali AND rozpoczecie < czas_start ORDER BY rozpoczecie DESC LIMIT 1;
END; $$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION czas_trwania(id_filmu integer)
    RETURNS integer AS
$$
BEGIN
    RETURN czas_trwania FROM kinorezerwacja_film WHERE id = id_filmu;
END; $$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION pobierz_id_filmu(id_seansu integer)
    RETURNS integer AS
$$
BEGIN
    RETURN id_filmu FROM kinorezerwacja_seans WHERE id = id_seansu;
END; $$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION pobierz_id_sali(id_miejsca integer)
    RETURNS integer AS
$$
BEGIN
    RETURN id_sali FROM kinorezerwacja_miejsce WHERE id = id_miejsca;
END; $$ LANGUAGE plpgsql;


CREATE OR REPLACE FUNCTION czas_rozpoczecia(id_seansu integer)
    RETURNS timestamp with time zone AS
$$
BEGIN
    RETURN rozpoczecie FROM kinorezerwacja_seans WHERE id = id_seansu;
END; $$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION czas_przerwy_tech(id_sali integer)
    RETURNS integer AS
$$
BEGIN
    RETURN czas_przerwy_technicznej FROM kinorezerwacja_sala WHERE id = id_sali;
END; $$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION czy_sie_nie_pokrywaja(
    rozp_pierw timestamp with time zone, czas_trw_pierw integer,
    rozp_drug timestamp with time zone, czas_przerwy integer)
    RETURNS integer AS
$$
BEGIN
    IF rozp_pierw + czas_trw_pierw * INTERVAL '1 MINUTE' 
        + czas_przerwy * INTERVAL '1 MINUTE' <= rozp_drug THEN
        RETURN 1;    
    ELSE RETURN 0;
    END IF;
END; $$ LANGUAGE plpgsql;

/* Sprawdza czy uzytkownik jest adminem: true/false */
CREATE OR REPLACE FUNCTION czy_uzytkownik_jest_adminem(id_usera integer)
    RETURNS boolean AS
$$
BEGIN
    IF (SELECT COUNT(*) FROM kinorezerwacja_ranga_klienta WHERE id_rangi = 1 AND id_klienta = id_usera) > 0 
        THEN RETURN TRUE;
    ELSE   
        RETURN FALSE;
    END IF;
END; $$ LANGUAGE plpgsql;

/* Funkcje pomocnicze do liczenia znizek */
CREATE OR REPLACE FUNCTION najlepsza_znizka(id_rezerwacja integer, typ varchar)
    RETURNS integer AS
$$
DECLARE
    tmp integer;
BEGIN
    SELECT max(znizka) INTO tmp
        FROM kinorezerwacja_promocja kr_p 
        CROSS JOIN kinorezerwacja_rezerwacja kr_r 
        JOIN kinorezerwacja_instancja_miejsca kr_im ON kr_r.id_instancji_miejsca = kr_im.id 
        JOIN kinorezerwacja_seans kr_s ON kr_im.id_seansu = kr_s.id
        CROSS JOIN kinorezerwacja_ranga_klienta kr_rk 
        WHERE kr_p.typ = typ
        AND kr_r.id = id_rezerwacja
        AND ((kr_rk.id_rangi = kr_p.id_rangi AND kr_rk.id_klienta = kr_r.id_klienta) OR kr_p.id_rangi = -1)
        AND kr_r.kiedy >= kr_p.od_rezerwacja
        AND (kr_p.do_rezerwacja IS NULL OR kr_p.do_rezerwacja >= kr_r.kiedy)
        AND kr_s.rozpoczecie >= kr_p.od_seans
        AND (kr_p.do_seans IS NULL OR kr_s.rozpoczecie <= kr_p.do_seans);
    IF tmp IS NULL THEN
        RETURN 0;
    ELSE
        RETURN tmp;
    END IF;     
END; $$ LANGUAGE plpgsql;    


CREATE OR REPLACE FUNCTION podaj_najlepsza_cena(id_rezerwacja integer)
    RETURNS numeric(10,3) AS
$$
DECLARE
    cena_org numeric(10,3);
    znizka_proc integer;
    znizka_stala integer;    
BEGIN  
    SELECT cena INTO cena_org
        FROM kinorezerwacja_rezerwacja kr_r JOIN kinorezerwacja_typ_biletu kr_tb ON kr_r.id_typu_biletu = kr_tb.id
        WHERE id_rezerwacja = kr_r.id;
    SELECT najlepsza_znizka(id_rezerwacja, 'procentowa') INTO znizka_proc;
    SELECT najlepsza_znizka(id_rezerwacja, 'stala') INTO znizka_stala;

    IF cena_org IS NULL THEN
        cena_org = 0;
    END IF;    
    IF ((cena_org - znizka_stala) < (cena_org * (100 - znizka_proc) / 100)) THEN
        IF (cena_org - znizka_stala < 1) THEN
            RETURN 1;
        ELSE
            RETURN cena_org - znizka_stala;
        END IF;         
    ELSE
        RETURN cena_org * (100 - znizka_proc) / 100;
    END IF;
        
END; $$ LANGUAGE plpgsql;    

/* Mozesz chciec dropa wykomentowac */
COMMIT;
DROP TYPE kinorez_rez_holder CASCADE;
BEGIN;

CREATE TYPE kinorez_rez_holder AS (kiedy timestamp with time zone, id integer, id_klienta integer, id_instancji_miejsca integer, id_typu_biletu integer, opis text, cena_oryginalna numeric(10,3), cena_po_promocji numeric(10, 3), rozpoczecie timestamp with time zone, tytul text);
/* Funkcja zwracaja tablice krotek typu kinorez_rez_holder - rezerwacje danego klienta posortowane po czasach rezerwacji.
    Sa to wszystkie rezerwacje w tabeli rezerwacje.
    Bez podzialu na aktualne / wykupione / przepadle.
    Potrzebuje miec co najmniej jeden wpis w tabeli kinorezerwacja_ranga_klient.
*/
CREATE OR REPLACE FUNCTION podaj_rezerwacja(id_kli integer)
  RETURNS SETOF kinorez_rez_holder  AS 
$$
DECLARE
    r kinorez_rez_holder%rowtype;
BEGIN
    for r in (
        SELECT kr_r.kiedy, kr_r.id, kr_r.id_klienta, kr_r.id_instancji_miejsca, kr_r.id_typu_biletu, kr_tb.opis, kr_tb.cena, podaj_najlepsza_cena(kr_r.id), kr_s.rozpoczecie, kr_f.tytul
        FROM kinorezerwacja_rezerwacja kr_r 
        JOIN kinorezerwacja_typ_biletu kr_tb 
            ON kr_r.id_typu_biletu = kr_tb.id 
        JOIN kinorezerwacja_instancja_miejsca kr_im
            ON kr_im.id = kr_r.id_instancji_miejsca
        JOIN kinorezerwacja_seans kr_s
            ON kr_im.id_seansu = kr_s.id
        JOIN kinorezerwacja_film kr_f
            ON kr_s.id_filmu = kr_f.id
        WHERE kr_r.id_klienta = id_kli 
        ORDER BY kr_r.kiedy
    ) LOOP
        RETURN NEXT r;
    END LOOP;
    RETURN;
END; $$ LANGUAGE plpgsql;
/* Przekazuje te z rezerwacji ktore faktycznie maja stan zarezerwowane */ 
CREATE OR REPLACE FUNCTION podaj_rezerwacja_ktore_maja_status_rezerwowane(id_kli integer)
  RETURNS SETOF kinorez_rez_holder  AS 
$$
DECLARE
    r kinorez_rez_holder%rowtype;
BEGIN
    for r in (
        SELECT * FROM podaj_rezerwacja(id_kli)
    ) LOOP
        IF (SELECT COUNT(*) FROM kinorezerwacja_instancja_miejsca kr_im 
        WHERE r.id_instancji_miejsca = kr_im.id AND stan = 'zarezerwowany' ) 
        > 0 THEN
            RETURN NEXT r;
        END IF;    
    END LOOP;
    RETURN;
END; $$ LANGUAGE plpgsql;


-- INSERT INTO 
-- SELECT * FROM podaj_rezerwacja(1);
-- SELECT * FROM kinorezerwacja_film;
-- SELECT * FROM kinorezerwacja_miejsce;

-- SELECT * FROM najlepsza_znizka(1, 'procentowa') ;
-- SELECT * FROM najlepsza_znizka(1, 'stala') ;


-- SELECT czy_uzytkownik_jest_adminem(10);

/* 
    Dla danego id_budynku, opisu sali, ilosc rzedow, ilosci miejsc, czasu przerwy technicznej - tworzy nowa sale o tych parametrach.
    Korzysta z currval/nextval od sekwencji. 
    Uwaga - jak wprowadzamy cos recznie to wlasnie z tego powodu moga nam sie dane (id) pogryzc.
*/
CREATE OR REPLACE FUNCTION dodaj_sale(mid_budynku integer, mopis text, mrzedow integer, mmiejsc_w_r integer, mczas_przerwy_technicznej integer) 
    RETURNS integer AS
$$
BEGIN
    INSERT INTO kinorezerwacja_sala(id_budynku, opis, czas_przerwy_technicznej) VALUES(mid_budynku, mopis, mczas_przerwy_technicznej);
    FOR i IN 1..mrzedow LOOP
        FOR j IN 1..mmiejsc_w_r LOOP
            INSERT INTO kinorezerwacja_miejsce(id_sali, rzad, miejsce) VALUES(CURRVAL('kinorezerwacja_sala_id_seq'), i, j);
        END LOOP;
    END LOOP;
    RETURN CURRVAL('kinorezerwacja_sala_id_seq');
END; $$ LANGUAGE plpgsql;

/*  Czy przedzialy sie przecinaja. Konce = null tzn inf.
    Przedzialy niezerowe.
*/
CREATE OR REPLACE FUNCTION czy_przedzialy_sie_przecinaja(
    beg1 timestamp with time zone, end1 timestamp with time zone,
    beg2 timestamp with time zone, end2 timestamp with time zone)
    RETURNS boolean AS
$$
BEGIN
    -- beg1 <= beg2 < end1
    IF beg1 <= beg2 AND (end1 IS NULL OR end1 > beg2) THEN 
        RETURN TRUE;
    -- beg2 <= beg1 < end2
    ELSEIF beg2 <= beg1 AND (end2 IS NULL OR end2 > beg1) THEN 
        RETURN TRUE;
    ELSE 
        RETURN FALSE;
    END IF;
END; $$ LANGUAGE plpgsql;    


/* Funkcja ktora dla danego przedzialu czasowego, id, typu (sali/miejsca) sprawdza czy ma ono awarie*/
CREATE OR REPLACE FUNCTION sprawdz_czy_cos_uleglo_awarii
    (czas_od timestamp with time zone, czas_do timestamp with time zone, id_czegos integer, typ_czegos text)
    RETURNS boolean AS
$$
BEGIN
    IF (SELECT COUNT(*) FROM kinorezerwacja_awaria kr_a 
        WHERE kr_a.czego = typ_czegos
        AND kr_a.id_obiektu = id_czegos
        AND czy_przedzialy_sie_przecinaja(kr_a.od, kr_a.do, czas_od, czas_do)
        ) > 0 THEN
            RETURN TRUE;
    ELSE RETURN FALSE;
    END IF;        
END; $$ LANGUAGE plpgsql;

/* 
    Dodaje seans. 
    Jesli jakies miejsce ma awarie - dodaje je ze stanem awaria.
    Jesli cala sala ma awarie - dodaje wszystkie miejsca ze stanem awaria.
*/

CREATE OR REPLACE FUNCTION dodaj_seans(mid_filmu integer, mid_sali integer, mrozpoczecie timestamp with time zone)
    RETURNS integer AS
$$
DECLARE
    r kinorezerwacja_miejsce%rowtype;
    awaria_sali boolean;
BEGIN
    SELECT * INTO awaria_sali FROM 
        sprawdz_czy_cos_uleglo_awarii(mrozpoczecie, 
        mrozpoczecie + czas_trwania(mid_filmu) * INTERVAL '1 MINUTE', 
        mid_sali, 'sali');
    INSERT INTO kinorezerwacja_seans(id_filmu, id_sali, rozpoczecie) VALUES(mid_filmu, mid_sali, mrozpoczecie);
    for r in (
        SELECT * FROM kinorezerwacja_miejsce WHERE id_sali = mid_sali
    ) LOOP
        IF awaria_sali THEN 
            INSERT INTO kinorezerwacja_instancja_miejsca(id_miejsca, id_seansu, stan) 
            VALUES(r.id, CURRVAL('kinorezerwacja_seans_id_seq'), 'niedostepny');
        ELSEIF sprawdz_czy_cos_uleglo_awarii(mrozpoczecie, 
            mrozpoczecie + czas_trwania(mid_filmu) * INTERVAL '1 MINUTE', r.id, 'miejsca') THEN
            INSERT INTO kinorezerwacja_instancja_miejsca(id_miejsca, id_seansu, stan) 
            VALUES(r.id, CURRVAL('kinorezerwacja_seans_id_seq'), 'niedostepny');
        ELSE
            INSERT INTO kinorezerwacja_instancja_miejsca(id_miejsca, id_seansu, stan) 
            VALUES(r.id, CURRVAL('kinorezerwacja_seans_id_seq'), 'wolny');
        END IF;
        
    END LOOP;
    RETURN CURRVAL('kinorezerwacja_seans_id_seq');
END; $$ LANGUAGE plpgsql;

/* Mozesz chciec dropa wykomentowac */
COMMIT;
DROP TYPE kinorez_widziane_filmy_holder CASCADE;
BEGIN;

CREATE TYPE kinorez_widziane_filmy_holder AS (tytul text);
/* Funkcja zwracaja tablice krotek typu kinorez_widziane_filmy_holder - filmy na ktore klient ma/mial rezerwacje.
    Sa to wszystkie info z tabeli rezerwacje.
    Bez podzialu na aktualne / wykupione / przepadle.
    Potrzebuje miec co najmniej jeden wpis w tabeli kinorezerwacja_ranga_klient.
*/
CREATE OR REPLACE FUNCTION podaj_filmy_rezerwowane(id_kli integer)
  RETURNS SETOF kinorez_widziane_filmy_holder AS 
$$
DECLARE
    r kinorez_widziane_filmy_holder%rowtype;
BEGIN
    for r in (
        SELECT DISTINCT(kr_f.tytul)
        FROM kinorezerwacja_rezerwacja kr_r 
        JOIN kinorezerwacja_instancja_miejsca kr_im
            ON kr_im.id = kr_r.id_instancji_miejsca
        JOIN kinorezerwacja_seans kr_s
            ON kr_im.id_seansu = kr_s.id
        JOIN kinorezerwacja_film kr_f
            ON kr_s.id_filmu = kr_f.id
        WHERE kr_r.id_klienta = id_kli 
        ORDER BY kr_f.tytul
    ) LOOP
        RETURN NEXT r;
    END LOOP;
    RETURN;
END; $$ LANGUAGE plpgsql;

/*
    Funkcja dodaje nowego klienta o randze 2. 
    Ranga 2 musi istniec!!!
    (Ranga 2 - podstawowa ranga klienta).
*/

CREATE OR REPLACE FUNCTION dodaj_nowego_klienta(mlogin text, mhaslo text, mimie text, mnazwisko text) 
    RETURNS integer AS
$$
BEGIN
    INSERT INTO kinorezerwacja_klient(login, haslo, imie, nazwisko) VALUES(mlogin, mhaslo, mimie, mnazwisko);
    INSERT INTO kinorezerwacja_ranga_klienta(id_rangi, id_klienta) VALUES(2, CURRVAL('kinorezerwacja_klient_id_seq'));
    RETURN CURRVAL('kinorezerwacja_klient_id_seq');
END; $$ LANGUAGE plpgsql;

/* DEBUG 
SELECT dodaj_sale(1, 'opisik', 4, 3, 30);
SELECT * FROM kinorezerwacja_sala;
SELECT * FROM kinorezerwacja_miejsce;
SELECT dodaj_seans(1, CAST(CURRVAL('kinorezerwacja_sala_id_seq') as integer), timestamp'2012-01-01');
SELECT * FROM kinorezerwacja_seans;
SELECT * FROM kinorezerwacja_instancja_miejsca;
SELECT * FROM podaj_filmy_rezerwowane(1);
*/
/*
    Zwraca ile maksymalnie jednoczenie rezerwacji moze miec dany uzytkownik.
    Jest to max po limitach rezerwacji przypisanych do jego rang. 
    Zwraca 0 jesli nie ma takich limitow.
    Zwraca -1 jesli user jest adminem. (Admin moze wszystko wiec to powinno byc rownowazne +inf).
*/
CREATE OR REPLACE FUNCTION ile_max_rezerwacji(id_kli integer)
    RETURNS integer AS
$$
BEGIN
    IF czy_uzytkownik_jest_adminem(id_kli) THEN
        RETURN -1;
    END IF;
    RETURN COALESCE(MAX(kr_lim.limit), 0)
        FROM kinorezerwacja_limit_rezerwacji kr_lim
        JOIN kinorezerwacja_ranga kr_r ON kr_r.id = kr_lim.id_rangi
        JOIN kinorezerwacja_ranga_klienta kr_rk ON kr_rk.id_rangi = kr_r.id
        WHERE kr_rk.id_klienta = id_kli;
END; $$ LANGUAGE plpgsql;
/* 
    Zwraca ile dany klient ma w tej chwili rezerwacji ktore maja stan 'zarezerwowany'
*/
CREATE OR REPLACE FUNCTION ile_rezerwacji_aktualnie(id_kli integer)
    RETURNS integer AS
$$
BEGIN
    RETURN COUNT(*) FROM podaj_rezerwacja_ktore_maja_status_rezerwowane(id_kli);
END; $$ LANGUAGE plpgsql;    

/* 
    Zwraca dla danej instancji miejsca czy jakas rezerwacja na nia wskazuje.
*/
CREATE OR REPLACE FUNCTION czy_jest_na_to_rezerwacja(tid_instancji_miejsca integer)
    RETURNS boolean AS
$$ BEGIN
    RETURN (SELECT COUNT(*) FROM kinorezerwacja_rezerwacja WHERE id_instancji_miejsca = tid_instancji_miejsca) > 0;
END; $$ LANGUAGE plpgsql;

/* 
    Dla awarii ustawia jej czas zakonczenia.
    Po czym dla kazdej instancji miejsca sprawdza czy nie mozna mu wrocic stanu wolnego.
    Jesli jest to wywolywane na awarii ktora juz ma jakis koniec to nie dzieje sie NIC. :)
*/ 
CREATE OR REPLACE FUNCTION zakoncz_awarie(id_awari integer, zakonczenie timestamp with time zone)
    RETURNS void AS
$$
DECLARE
    prev timestamp with time zone;
BEGIN
    SELECT kr_a.do INTO prev FROM kinorezerwacja_awaria kr_a WHERE id = id_awari;
    IF prev IS NOT NULL THEN
        RETURN;
    END IF;    
    UPDATE kinorezerwacja_awaria kr_a SET kr_a.do = zakonczenie
        WHERE id = id_awari;
    UPDATE kinorezerwacja_instancja_miejsca SET stan = 'wolny'
        WHERE stan = 'awaria'
        AND (czy_jest_na_to_rezerwacja(id) = FALSE)
        AND (sprawdz_czy_cos_uleglo_awarii(
        czas_rozpoczecia(id_seansu), 
        czas_rozpoczecia(id_seansu) + (czas_trwania(pobierz_id_filmu(id_seansu)) * INTERVAL '1 MINUTE'),
        id_miejsca, 'miejsca') = FALSE)
        AND (sprawdz_czy_cos_uleglo_awarii(
        czas_rozpoczecia(id_seansu), 
        czas_rozpoczecia(id_seansu) + (czas_trwania(pobierz_id_filmu(id_seansu)) * INTERVAL '1 MINUTE'),
        pobierz_id_sali(id_miejsca), 'sali') = FALSE)
        ;
    UPDATE kinorezerwacja_instancja_miejsca SET stan = 'zarezerwowany'
        WHERE stan = 'awaria'
        AND (czy_jest_na_to_rezerwacja(id) = TRUE)
        AND (sprawdz_czy_cos_uleglo_awarii(
        czas_rozpoczecia(id_seansu), 
        czas_rozpoczecia(id_seansu) + (czas_trwania(pobierz_id_filmu(id_seansu)) * INTERVAL '1 MINUTE'),
        id_miejsca, 'miejsca') = FALSE)
        AND (sprawdz_czy_cos_uleglo_awarii(
        czas_rozpoczecia(id_seansu), 
        czas_rozpoczecia(id_seansu) + (czas_trwania(pobierz_id_filmu(id_seansu)) * INTERVAL '1 MINUTE'),
        pobierz_id_sali(id_miejsca), 'sali') = FALSE)
        ;


    RETURN;       
END; $$ LANGUAGE plpgsql;
/* 
    Ustaw awarie. 
    Moze byc na przedziale <beg, end> albo <beg, null) co oznacza <beg, inf).
    Nie dodaje faktycznie awari. Tylko RULE jest przeladowany na ON INSERT i dodatkowo to robi.
    Jesli jakies miejsca sa juz zarezerwowane to trudno, rezerwacja zostaje, wyswietla mu sie tam w tabelce niedostepne. ;)
*/

CREATE OR REPLACE FUNCTION wlacz_awarie()
    RETURNS integer AS
$$
BEGIN
    UPDATE kinorezerwacja_instancja_miejsca SET stan = 'niedostepny'
        WHERE 
        sprawdz_czy_cos_uleglo_awarii(
        czas_rozpoczecia(id_seansu), 
        czas_rozpoczecia(id_seansu) + (czas_trwania(pobierz_id_filmu(id_seansu)) * INTERVAL '1 MINUTE'),
        id_miejsca, 'miejsca')
        OR sprawdz_czy_cos_uleglo_awarii(
        czas_rozpoczecia(id_seansu), 
        czas_rozpoczecia(id_seansu) + (czas_trwania(pobierz_id_filmu(id_seansu)) * INTERVAL '1 MINUTE'),
        pobierz_id_sali(id_miejsca), 'sali')
        ;
    RETURN CURRVAL('kinorezerwacja_awaria_id_seq');    
END; $$ LANGUAGE plpgsql;    


-- ROLLBACK;
COMMIT;
